package yawning.mew.sim;

import java.util.Map;

import yawning.mew.ProcEffect;
import yawning.mew.ProcEffect.Effect;
import yawning.mew.character.CharacterModel;

public class SimulationProc extends SimulationEvent {
	ProcEffect mEffect;
	SimulationProcStatus mStatus;
	CharacterModel mModel;
	private double mIcdExpireTime;
	private SimulationTarget mTriggerTarget;
	private boolean mIsDamageProc;
	private boolean mIsSpellIdProc;
	public int mNrStacks = 0;
	public int mNrProcs = 0;

	public interface EffectCallback {
		public void onEffect(SimulationTarget target, int n);
	}

	public SimulationProc(SimulationEngine su, CharacterModel model, ProcEffect effect) {
		super(su);
		mModel = model;
		mEffect = effect;
		mStatus = new SimulationProcStatus(this);
		if (mEffect.buffStackMax < 1) mEffect.buffStackMax = 1;
		if (mEffect.get(Effect.SPELL_DAMAGE) > 0 || mEffect.get(Effect.PHYSICAL_DAMAGE) > 0) mIsDamageProc = true;
		if (mEffect.get(Effect.SPELL_ID) != 0) mIsSpellIdProc = true;
	}

	public void onProc(SimulationTarget target) {
		if (mIcdExpireTime > mSimEngine.getSimTime()) return;
		if (mIsDamageProc && target == null) return;
		if (mEffect.buffLinkedCooldown > 0 && mSimEngine.mLinkedProcCDTime > mSimEngine.getSimTime()) return;
		if (mEffect.rate > 0 && !mSimEngine.getRandom().nextBoolean(mEffect.rate)) return;
		if (mEffect.ppm > 0 && !mSimEngine.getRandom().nextBoolean((mModel.getBaseWeaponSpeed()/60) * mEffect.ppm)) return;

		// Called when a proc is triggered.  Cooldown checking done elsewhere.
		if (mEffect.buffCooldown > 0) mIcdExpireTime = mSimEngine.getSimTime() + mEffect.buffCooldown;
		if (mEffect.buffLinkedCooldown > 0) mSimEngine.mLinkedProcCDTime = mSimEngine.getSimTime() + mEffect.buffLinkedCooldown + 0.0001;	// HACK
		mNrProcs++;

		// Only want to increment stats if the buff won't be a refresh.
		if (mNrStacks < mEffect.buffStackMax) {
			// Log first so that procs that dump spew can also join in without looking terrible.
			if (mSimEngine.isLogging()) {
				if (mEffect.buffStackMax > 1) {
					mSimEngine.getLog().log("Proc Trigger: %s Stacks: %d", mEffect.name, (mNrStacks + 1));
				} else {
					mSimEngine.getLog().log("Proc Trigger: %s", mEffect.name);
				}
			}

			if (mIsSpellIdProc) {
				mSimEngine.mEffectCallbacks.get(Effect.SPELL_ID).onEffect(target, mEffect.get(Effect.SPELL_ID));
			} else {
				for (Map.Entry<Effect,Integer> e : mEffect.mEffects.entrySet()) {
					mSimEngine.mEffectCallbacks.get(e.getKey()).onEffect(target, e.getValue());
				}
			}
			mNrStacks++;

			if (mSimEngine.isLogging()) mSimEngine.getLog().logAppend("\n");
		} else {
			if (mSimEngine.isLogging()) {
				mSimEngine.getLog().log("Proc Refresh: %s Expiration: %.2f\n", mEffect.name, mSimEngine.getSimTime() + mEffect.buffDuration);
			}
		}

		mTriggerTarget = target;

		schedule(mEffect.buffDuration);

		updateProcStatus();
	}

	@Override
	public void onExpire() {
		if (mIsSpellIdProc) {
			mSimEngine.mEffectCallbacks.get(Effect.SPELL_ID).onEffect(mTriggerTarget, - mEffect.get(Effect.SPELL_ID));
		} else {
			for (Map.Entry<Effect,Integer> e : mEffect.mEffects.entrySet()) {
				mSimEngine.mEffectCallbacks.get(e.getKey()).onEffect(mTriggerTarget, - e.getValue() * mNrStacks);
			}
		}
		mNrStacks = 0;

		if (mSimEngine.isLogging() && mEffect.buffDuration > 0) {
			mSimEngine.getLog().log("Proc Expiration: %s\n", mEffect.name);
		}

		updateProcStatus();
	}

	private void updateProcStatus() {
		mStatus.setCooldownExpirationTime(mIcdExpireTime);
		mStatus.setBuffExpirationTime(getExpiryTime());
		mStatus.setBuffStacks(mNrStacks);
	}

	public ProcEffect getProcEffect() {
		return mEffect;
	}
}
